package bancosys.tec.persist.hibernate.type;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import jmine.tec.persist.PersistMessages;

import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;

import bancosys.tec.persist.base.enumx.CouldNotGetEnumException;
import bancosys.tec.utils.reflection.ReflectionUtils;

/**
 * Conversor para do hibernate para o tipo {@link StaticIdEnum}. É necessário espeficiar a classe do enum para qual esse conversor se refere
 * através do parametro da anotação @Type 'parameters' com a chave 'enumClass'.
 * 
 * @author piercio
 */
public class StaticIdEnumType implements UserType, ParameterizedType {

    public static final String ENUM_CLASS = "enumClass";

    private Class<? extends StaticIdEnum> enumClass;

    private final Map<Integer, StaticIdEnum> valuesCache = new HashMap<Integer, StaticIdEnum>();

    /**
     * Construtor.
     */
    public StaticIdEnumType() {
        super();
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public void setParameterValues(Properties parameters) {
        try {
            String type = parameters.getProperty(ENUM_CLASS);
            this.enumClass = (Class<? extends StaticIdEnum>) ReflectionUtils.findClass(type);
            StaticIdEnum[] values = this.enumClass.getEnumConstants();
            for (StaticIdEnum staticIdEnum : values) {
                this.valuesCache.put(staticIdEnum.getId(), staticIdEnum);
            }
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * {@inheritDoc}
     */
    public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws SQLException {
        Integer enumId = rs.getInt(names[0]);
        if (rs.wasNull()) {
            return null;
        }
        StaticIdEnum value = this.valuesCache.get(enumId);
        if (value != null) {
            return value;
        }
        throw new CouldNotGetEnumException(PersistMessages.COULD_NOT_GET_ENUM_NOT_FOUND.create(this.enumClass, enumId));
    }

    /**
     * {@inheritDoc}
     */
    public void nullSafeSet(PreparedStatement st, Object value, int index) throws SQLException {
        if (value == null) {
            st.setNull(index, Types.INTEGER);
        } else {
            st.setInt(index, ((StaticIdEnum) value).getId());
        }

    }

    /**
     * {@inheritDoc}
     */
    public Object assemble(Serializable cached, Object owner) {
        return cached;
    }

    /**
     * {@inheritDoc}
     */
    public Object deepCopy(Object value) {
        return value;
    }

    /**
     * {@inheritDoc}
     */
    public Serializable disassemble(Object value) {
        return (Serializable) value;
    }

    /**
     * {@inheritDoc}
     */
    public boolean equals(Object x, Object y) {
        return x == y;
    }

    /**
     * {@inheritDoc}
     */
    public int hashCode(Object x) {
        return x.hashCode();
    }

    /**
     * {@inheritDoc}
     */
    public boolean isMutable() {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public Object replace(Object original, Object target, Object owner) {
        return original;
    }

    /**
     * {@inheritDoc}
     */
    public Class returnedClass() {
        return this.enumClass;
    }

    /**
     * {@inheritDoc}
     */
    public int[] sqlTypes() {
        return new int[]{ Types.INTEGER };
    }

}
